package com.ikingtech.platform.gateway.filter;

import com.ikingtech.framework.sdk.authenticate.operation.IdentityValidator;
import com.ikingtech.framework.sdk.cache.constants.CacheConstants;
import com.ikingtech.framework.sdk.context.constant.CommonConstants;
import com.ikingtech.framework.sdk.context.security.Identity;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.platform.gateway.properties.GatewayProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.Charset;
import java.util.List;

import static com.ikingtech.framework.sdk.context.constant.SecurityConstants.*;

/**
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
public class AuthenticationGlobalFilter implements GlobalFilter, Ordered {

    private final IdentityValidator identityValidator;

    private final AntPathMatcher matcher = new AntPathMatcher();

    private final GatewayProperties gatewayProperties;

    private final StringRedisTemplate redisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest serverHttpRequest = exchange.getRequest();

        Identity identity;
        for (String pathPattern : IdentityValidator.getDefaultIgnorePaths()) {
            if (this.matcher.match(pathPattern, serverHttpRequest.getURI().getPath())) {
                this.setIdentityHeader(Identity.defaultUser(), exchange);
                return chain.filter(exchange);
            }
        }
        if (Tools.Coll.isNotBlank(this.gatewayProperties.getIgnore())) {
            for (String pathPattern : this.gatewayProperties.getIgnore()) {
                if (this.matcher.match(pathPattern, serverHttpRequest.getURI().getPath())) {
                    this.setIdentityHeader(Identity.defaultUser(), exchange);
                    return chain.filter(exchange);
                }
            }
        }
        identity = this.identityValidator.validate(serverHttpRequest.getQueryParams(), serverHttpRequest.getHeaders());
        ServerHttpResponse response = exchange.getResponse();
        if (null == identity) {
            return response.writeWith(this.unAuthentication(response, "loginFirst"));
        }

        String tenantCode = serverHttpRequest.getHeaders().getFirst(HEADER_TENANT_CODE);
        if (this.isFrozenTenant(tenantCode)) {
            return response.writeWith(this.unAuthentication(response, "currentFreeze"));
        }

        this.setIdentityHeader(identity, exchange);
        return chain.filter(exchange);
    }

    private boolean isFrozenTenant(String tenantCode) {
        if (Tools.Str.isBlank(tenantCode) || DEFAULT_TENANT_CODE.equals(tenantCode)) {
            return false;
        }
        List<String> frozenTenant = this.redisTemplate.opsForList().range(CacheConstants.ABNORMAL_TENANT, 0, -1);
        if (frozenTenant == null) {
            return false;
        }
        return frozenTenant.contains(tenantCode);
    }

    private void setIdentityHeader(Identity identity, ServerWebExchange exchange) {
        String requestId = this.identityValidator.cacheValidatedIdentity(identity);
        ServerHttpRequest newRequest = exchange.getRequest().mutate().headers(header -> {
            header.add(HEADER_GATEWAY_REQUEST_ID, requestId);
            header.add(HEADER_CALLER, "INNER");
        }).build();
        exchange.mutate().request(newRequest).build();
    }

    private Mono<DataBuffer> unAuthentication(ServerHttpResponse response, String reason) {
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, "application/json");
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        return Mono.just(response.bufferFactory().wrap(Tools.Json.toJsonStr(R.failed(reason)).getBytes(Charset.defaultCharset())));
    }

    @Override
    public int getOrder() {
        return CommonConstants.AUTHENTICATION_FILTER_ORDER;
    }
}
